/*
 *
 * HDF is dual licensed: you can use it either under the terms of
 * the GPL, or the BSD license, at your option.
 * See the LICENSE file in the root of this repository for complete details.
 */

#include "hdf_drm_panel_simple.h"
#include <drm/drm_device.h>
#include <drm/drm_atomic_helper.h>
#include <linux/backlight.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <video/mipi_display.h>
#include <video/of_display_timing.h>
#include <video/videomode.h>
#include <uapi/drm/drm_mode.h>
#include "osal_mem.h"
#include "osal.h"

static inline struct panel_simple *to_panel_simple(const struct drm_panel *panel)
{
    return container_of(panel, struct panel_simple, panel);
}

static int HdfDrmPanelSimpleUnprepare(struct drm_panel *panel)
{
    struct panel_simple *simpePanel = to_panel_simple(panel);
    struct PanelData *panelData;
    struct DispManager *manager = GetDispManager();

    HDF_LOGD("HdfDrmPanelSimpleUnprepare");
    if (simpePanel->index >= PANEL_MAX) {
        HDF_LOGE("panel num out of PANEL_MAX");
        return HDF_FAILURE;
    }
    OsalMutexLock(&manager->dispMutex);
    panelData = manager->panelManager->panel[simpePanel->index];
    panelData->unprepare(panelData);
    OsalMutexUnlock(&manager->dispMutex);
    return HDF_SUCCESS;
}

static int HdfDrmPanelSimplePrepare(struct drm_panel *panel)
{
    struct panel_simple *simpePanel = to_panel_simple(panel);
    struct PanelData *panelData;
    struct DispManager *manager = GetDispManager();

    HDF_LOGD("HdfDrmPanelSimplePrepare");
    if (simpePanel->index >= PANEL_MAX) {
        HDF_LOGE("panel num out of PANEL_MAX");
        return HDF_FAILURE;
    }
    OsalMutexLock(&manager->dispMutex);
    panelData = manager->panelManager->panel[simpePanel->index];
    panelData->prepare(panelData);
    OsalMutexUnlock(&manager->dispMutex);
    return HDF_SUCCESS;
}

static int HdfDrmPanelSimpleDisable(struct drm_panel *panel)
{
    struct panel_simple *simpePanel = to_panel_simple(panel);
    struct PanelData *panelData;
    struct DispManager *manager = GetDispManager();

    HDF_LOGD("HdfDrmPanelSimpleDisable");
    if (simpePanel->index >= PANEL_MAX) {
        HDF_LOGE("panel num out of PANEL_MAX");
        return HDF_FAILURE;
    }
    OsalMutexLock(&manager->dispMutex);
    panelData = manager->panelManager->panel[simpePanel->index];
    panelData->off(panelData);
    OsalMutexUnlock(&manager->dispMutex);
    return HDF_SUCCESS;
}

static int HdfDrmPanelSimpleEnable(struct drm_panel *panel)
{
    struct panel_simple *simpePanel = to_panel_simple(panel);
    struct PanelData *panelData;
    struct DispManager *manager = GetDispManager();
    
    HDF_LOGD("HdfDrmPanelSimpleEnable");
    if (simpePanel->index >= PANEL_MAX) {
        HDF_LOGE("panel num out of PANEL_MAX");
        return HDF_FAILURE;
    }
    panelData = manager->panelManager->panel[simpePanel->index];
    OsalMutexLock(&manager->dispMutex);
    panelData->on(panelData);
    OsalMutexUnlock(&manager->dispMutex);
    return HDF_SUCCESS;
}

#define MIN_LEVEL                 0
#define MAX_LEVEL                 255
#define DEFAULT_LEVEL             100

static int32_t SetPanelInfo(struct panel_simple *simplePanel, struct drm_display_mode *mode)
{
    HDF_LOGI("SetPanelInfo enter");

    struct PanelData *panel = &simplePanel->panel_data;

    panel->info->clockFreq = mode->clock * 1000;
    panel->info->width = mode->hdisplay;
    panel->info->height = mode->vdisplay;
    panel->info->hbp = mode->htotal - mode->hsync_end;
    panel->info->hfp = mode->hsync_start - mode->hdisplay;
    panel->info->hsw = mode->hsync_end - mode->hsync_start;
    panel->info->vbp = mode->vtotal - mode->vsync_end;
    panel->info->vfp = mode->vsync_start - mode->vdisplay;
    panel->info->vsw = mode->vsync_end - mode->vsync_start;
    // panel->info->intfType = LCD_24BIT;
    // panel->info->intfSync = 0;
    // panel->info->frameRate = 0;
    panel->info->blk.type = BLK_PWM;
    panel->info->blk.minLevel = MIN_LEVEL;
    panel->info->blk.maxLevel = MAX_LEVEL;
    panel->info->blk.defLevel = DEFAULT_LEVEL;
    return HDF_SUCCESS;
}

static unsigned int panel_simple_get_timings_modes(struct panel_simple *panel,
						   struct drm_connector *connector)
{
	struct drm_display_mode *mode;
	unsigned int i, num = 0;

	for (i = 0; i < panel->desc->num_timings; i++) {
		const struct display_timing *dt = &panel->desc->timings[i];
		struct videomode vm;

		videomode_from_timing(dt, &vm);
		mode = drm_mode_create(connector->dev);
		if (!mode) {
			HDF_LOGE("failed to add mode %ux%u\n", 
                dt->hactive.typ, dt->vactive.typ);
			continue;
		}

		drm_display_mode_from_videomode(&vm, mode);

		mode->type |= DRM_MODE_TYPE_DRIVER;

		if (panel->desc->num_timings == 1)
			mode->type |= DRM_MODE_TYPE_PREFERRED;

		drm_mode_probed_add(connector, mode);
		num++;

        HDF_LOGI("panel_simple_get_timings_modes SetPanelInfo:%d", i);
        SetPanelInfo(panel, mode);
	}

	return num;
}

static unsigned int panel_simple_get_display_modes(struct panel_simple *panel,
						   struct drm_connector *connector)
{
	struct drm_display_mode *mode;
	unsigned int i, num = 0;

    HDF_LOGI("panel_simple_get_display_modes enter");

	for (i = 0; i < panel->desc->num_modes; i++) {
		const struct drm_display_mode *m = &panel->desc->modes[i];

		mode = drm_mode_duplicate(connector->dev, m);
		if (!mode) {
			HDF_LOGE("failed to add mode %ux%u@%u\n",
				m->hdisplay, m->vdisplay,
				drm_mode_vrefresh(m));
			continue;
		}

		mode->type |= DRM_MODE_TYPE_DRIVER;

		if (panel->desc->num_modes == 1)
			mode->type |= DRM_MODE_TYPE_PREFERRED;

		drm_mode_set_name(mode);

		drm_mode_probed_add(connector, mode);
		num++;

        HDF_LOGI("panel_simple_get_display_modes SetPanelInfo:%d num:%d", i, num);
        SetPanelInfo(panel, mode);
	}

	return num;
}

static int panel_simple_get_non_edid_modes(struct panel_simple *panel,
					   struct drm_connector *connector)
{
	struct drm_display_mode *mode;
	bool has_override = panel->override_mode.type;
	unsigned int num = 0;

	if (!panel->desc)
		return 0;

	if (has_override) {
		mode = drm_mode_duplicate(connector->dev,
					  &panel->override_mode);
		if (mode) {
			drm_mode_probed_add(connector, mode);
			num = 1;

            HDF_LOGI("panel_simple_get_non_edid_modes SetPanelInfo");
            SetPanelInfo(panel, mode);
		} else {
			HDF_LOGE("failed to add override mode\n");
		}
	}

	/* Only add timings if override was not there or failed to validate */
	if (num == 0 && panel->desc->num_timings)
		num = panel_simple_get_timings_modes(panel, connector);

	/*
	 * Only add fixed modes if timings/override added no mode.
	 *
	 * We should only ever have either the display timings specified
	 * or a fixed mode. Anything else is rather bogus.
	 */
	WARN_ON(panel->desc->num_timings && panel->desc->num_modes);
	if (num == 0)
		num = panel_simple_get_display_modes(panel, connector);

	if (panel->desc->bpc)
		connector->display_info.bpc = panel->desc->bpc;
	if (panel->desc->size.width)
		connector->display_info.width_mm = panel->desc->size.width;
	if (panel->desc->size.height)
		connector->display_info.height_mm = panel->desc->size.height;
	if (panel->desc->bus_format)
		drm_display_info_set_bus_formats(&connector->display_info,
						 &panel->desc->bus_format, 1);
	if (panel->desc->bus_flags)
		connector->display_info.bus_flags = panel->desc->bus_flags;

	return num;
}

static int HdfDrmPanelSimpleGetModes(struct drm_panel *panel, struct drm_connector *connector)
{
    struct panel_simple *p = to_panel_simple(panel);
    int num = 0;
    
    HDF_LOGI("HdfDrmPanelSimpleGetModes");

    if (panel == NULL) {
        HDF_LOGE("panel is NULL");
        return 0;
    }
    if (connector == NULL) {
        HDF_LOGE("connector is NULL");
        return 0;
    }

    // /* probe EDID if a DDC bus is available */
	// if (p->ddc) {
	// 	struct edid *edid = drm_get_edid(connector, p->ddc);

	// 	drm_connector_update_edid_property(connector, edid);
	// 	if (edid) {
	// 		num += drm_add_edid_modes(connector, edid);
	// 		kfree(edid);
	// 	}
	// }

	/* add hard-coded panel modes */
	num += panel_simple_get_non_edid_modes(p, connector);

	// /* set up connector's "panel orientation" property */
	// drm_connector_set_panel_orientation(connector, p->orientation);

	return num;
}

static int HdfDrmPanelSimpleGetTimings(struct drm_panel *panel,
				    unsigned int num_timings,
				    struct display_timing *timings)
{
    struct panel_simple *p = to_panel_simple(panel);
	unsigned int i;

	if (p->desc->num_timings < num_timings)
		num_timings = p->desc->num_timings;

	if (timings)
		for (i = 0; i < num_timings; i++)
			timings[i] = p->desc->timings[i];

	return p->desc->num_timings;
}

static struct drm_panel_funcs g_hdfDrmPanelFuncs = {
    .disable = HdfDrmPanelSimpleDisable,
    .unprepare = HdfDrmPanelSimpleUnprepare,
    .prepare = HdfDrmPanelSimplePrepare,
    .enable = HdfDrmPanelSimpleEnable,
    .get_modes = HdfDrmPanelSimpleGetModes,
    .get_timings = HdfDrmPanelSimpleGetTimings,
};

static ssize_t SuspendStore(struct device *dev,
    struct device_attribute *attr, const char *buf, size_t count)
{
    int32_t ret;
    struct panel_simple *simpePanel = dev_get_drvdata(dev);

    ret = HdfDrmPanelSimpleDisable(&simpePanel->panel);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("%s HdfDrmPanelSimpleDisable fail", __func__);
        return count;
    }
    ret = HdfDrmPanelSimpleUnprepare(&simpePanel->panel);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("%s HdfDrmPanelSimpleUnprepare fail", __func__);
        return count;
    }
    return count;
}
static DEVICE_ATTR(suspend, S_IWUSR, NULL, SuspendStore);

static ssize_t ResumeStore(struct device *dev,
    struct device_attribute *attr, const char *buf, size_t count)
{
    int32_t ret;
    struct panel_simple *simpePanel = dev_get_drvdata(dev);

    ret = HdfDrmPanelSimplePrepare(&simpePanel->panel);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("%s HdfDrmPanelSimplePrepare fail", __func__);
        return count;
    }
    ret = HdfDrmPanelSimpleEnable(&simpePanel->panel);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("%s HdfDrmPanelSimpleEnable fail", __func__);
        return count;
    }
    return count;
}
static DEVICE_ATTR(resume, S_IWUSR, NULL, ResumeStore);

static ssize_t BacklightStore(struct device *dev,
    struct device_attribute *attr, const char *buf, size_t count)
{
    int32_t ret;
    unsigned long level;
    struct PanelData *panelData = NULL;
    struct panel_simple *simpePanel = dev_get_drvdata(dev);
    struct DispManager *manager = GetDispManager();

    ret = kstrtoul(buf, 0, &level);
    if (ret != 0) {
        return ret;
    }
    HDF_LOGI("%s enter", __func__);
    OsalMutexLock(&manager->dispMutex);
    panelData = manager->panelManager->panel[simpePanel->index];
    OsalMutexUnlock(&manager->dispMutex);
    ret = UpdateBrightness(panelData->blDev, level);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("%s UpdateBrightness fail", __func__);
    }

    return count;
}
static DEVICE_ATTR(backlight, S_IWUSR, NULL, BacklightStore);

#define ATTR_NUM  3
static struct device_attribute *g_panelAttrs[] = {
    &dev_attr_suspend,
    &dev_attr_resume,
    &dev_attr_backlight,
    NULL,
};

static int32_t HdfDrmPanelSimpleEntryInit(struct HdfDeviceObject *object)
{
    (void)object;
    uint32_t ret;
    uint32_t i;
    uint32_t j;
    uint32_t panelNum;
    struct panel_simple *simplePanel = NULL;
    struct DispManager *manager = NULL;
    struct drm_panel *panel = NULL;
    struct device *dev = NULL;

    manager = GetDispManager();
    if (manager == NULL) {
        HDF_LOGE("%s manager is null", __func__);
        return HDF_FAILURE;
    }
    panelNum = manager->panelManager->panelNum;
    for (i = 0; i < panelNum; i++) {
        simplePanel = (struct panel_simple *)manager->panelManager->panel[i]->priv;
        simplePanel->index = i;
        panel = &simplePanel->panel;
        dev = panel->dev;

        drm_panel_init(panel, dev, &g_hdfDrmPanelFuncs, simplePanel->desc->connector_type);

        ret = drm_panel_of_backlight(panel);
        if (ret){
            HDF_LOGE("%s drm_panel_of_backlight failed %d", __func__, ret);
            drm_panel_remove(panel);
            return ret;
        }

        drm_panel_add(panel);

        dev_set_drvdata(dev, simplePanel);

        for (j = 0; j < ATTR_NUM; j++) {
            if (device_create_file(dev, g_panelAttrs[j]) != 0) {
                HDF_LOGE("%s line = %d device_create_file fail", __func__, __LINE__);
            }
        }

        HDF_LOGI("%s panel[%d] registered success", __func__, i);
    }
    HDF_LOGI("%s success", __func__);
    return HDF_SUCCESS;
}

struct HdfDriverEntry g_hdfDrmPanelSimpleEntry = {
    .moduleVersion = 1,
    .moduleName = "HDF_DRM_PANEL_SIMPLE",
    .Init = HdfDrmPanelSimpleEntryInit,
};

HDF_INIT(g_hdfDrmPanelSimpleEntry);
